﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTaskLib
{
    public class TaskManager
    {
        private readonly object SyncLock = new object();

        public TaskManager(int cycleDelay = 1000, int completeRemovalDelayCycles = 2, int incompleteRemovalDelayCycles = 5)
        {
            CycleDelay = cycleDelay;
            CompleteRemovalDelayCycles = completeRemovalDelayCycles;
            IncompleteRemovalDelayCycles = incompleteRemovalDelayCycles;

            Tasks = new List<BackgroundTask>();
            UIScheduler = TaskScheduler.FromCurrentSynchronizationContext();

            var task = Queue(new BackgroundTask(TaskRunner, "Task Runner", tag: BackgroundTask.SystemTaskTag));
            task.Run();
        }

        internal List<BackgroundTask> Tasks { get; private set; }
        internal TaskScheduler UIScheduler { get; private set; }

        internal int CycleDelay { get; private set; }
        internal int CompleteRemovalDelayCycles { get; private set; }
        internal int IncompleteRemovalDelayCycles { get; private set; }

        public BackgroundTask Queue(BackgroundTask task)
        {
            lock (SyncLock)
            {
                Tasks.Add(task);
            }

            task.Queued(this);

            return task;
        }

        public BackgroundTask QueueAfter(BackgroundTask parent, BackgroundTask task)
        {
            parent.Task.ContinueWith(_ => Queue(task));

            return task;
        }

        public List<BackgroundTask> GetTasks(Func<BackgroundTask, bool> filter = null)
        {
            List<BackgroundTask> tasks = null;

            lock (SyncLock)
            {
                tasks = filter == null ? Tasks.ToList() : Tasks.Where(filter).ToList();
            }

            return tasks;
        }

        internal void RemoveTask(BackgroundTask task)
        {
            lock (SyncLock)
            {
                Tasks.Remove(task);
            }
        }

        private void TaskRunner(Func<double, string, bool> progressAction)
        {
            while (progressAction(-1, null) == false)
            {
                for (var i = 0; i < Tasks.Count; ++i)
                {
                    var task = Tasks[i];

                    switch (task.Status)
                    {
                        case TaskStatus.Queued:
                            task.Run();
                            break;
                        case TaskStatus.Completed:
                        case TaskStatus.Cancelled:
                        case TaskStatus.Exception:
                            CompletedTask.AddCompletedTask(this, task);
                            break;
                    }
                }

                CompletedTask.RemoveCompletedTasks(this);

                Thread.Sleep(CycleDelay);
            }

            progressAction(1, null);
        }
    }

    class CompletedTask
    {
        private static List<CompletedTask> CompletedTasks = new List<CompletedTask>();
        private static HashSet<Guid> CompletedTaskSet = new HashSet<Guid>();

        private CompletedTask(BackgroundTask task, int completeRemovalDelayCycles, int incompleteRemovalDelayCycles)
        {
            Task = task;

            RemainingCleanupIterations = task.Status == TaskStatus.Completed ? 2 : 5;
        }

        public BackgroundTask Task { get; private set; }
        public int RemainingCleanupIterations { get; private set; }

        private bool CanRemove()
        {
            var canRemove = false;

            if (RemainingCleanupIterations-- == 0)
            {
                canRemove = true;
            }

            return canRemove;
        }

        public static void AddCompletedTask(TaskManager manager, BackgroundTask task)
        {
            if (CompletedTaskSet.Contains(task.Id) == false)
            {
                CompletedTaskSet.Add(task.Id);
                CompletedTasks.Add(new CompletedTask(task, manager.CompleteRemovalDelayCycles, manager.IncompleteRemovalDelayCycles));
            }
        }

        public static void RemoveCompletedTasks(TaskManager manager)
        {
            for (var i = 0; i < CompletedTasks.Count; ++i)
            {
                var task = CompletedTasks[i];

                if (task.CanRemove())
                {
                    manager.RemoveTask(task.Task);

                    CompletedTasks.RemoveAt(i);
                    CompletedTaskSet.Remove(task.Task.Id);
                }
            }
        }
    }
}
